package com.amaze.escape.generators;

import com.amaze.escape.Configuration;
import com.amaze.escape.Helper;
import com.amaze.escape.world.Chunk;

/**
 * Generates the base terrain of the world.
 * 
 * @author Amaze Team
 */
public class ChunkGeneratorTerrain extends ChunkGenerator {

    /**
     * 
     */
    public static int INTERPOLATION_INTERVAL = 8;

    /**
     *
     * @param seed
     */
    public ChunkGeneratorTerrain(String seed) {
        super(seed);
    }

    /**
     *
     * @param c
     */
    @Override
    public void generate(Chunk c) {
        float[][] heightMap = new float[(int) Configuration.CHUNK_DIMENSIONS.x + 1][(int) Configuration.CHUNK_DIMENSIONS.z + 1];


        /*
         * Calculate the height map.
         */
        for (int x = 0; x <= Configuration.CHUNK_DIMENSIONS.x; x++) {
            for (int y = 0; y <= Configuration.CHUNK_DIMENSIONS.z; y++) {
                if (x % INTERPOLATION_INTERVAL == 0 && y % INTERPOLATION_INTERVAL == 0) {
                    float height = calcHeightMap(x + getOffsetX(c), y + getOffsetZ(c));
                    heightMap[x][y] = height;
                }
            }
        }

        /*
         * Bilinear interpolate the missing values.
         */
        for (int y = 0; y < Configuration.CHUNK_DIMENSIONS.z; y++) {
            for (int x = 0; x < Configuration.CHUNK_DIMENSIONS.x; x++) {

                if (!(x % INTERPOLATION_INTERVAL == 0 && y % INTERPOLATION_INTERVAL == 0)) {
                    int offsetX = (x / INTERPOLATION_INTERVAL) * INTERPOLATION_INTERVAL;
                    int offsetY = (y / INTERPOLATION_INTERVAL) * INTERPOLATION_INTERVAL;
                    heightMap[x][y] = Helper.getInstance().bilinearInterpolation(x, y, heightMap[offsetX][offsetY], heightMap[offsetX][INTERPOLATION_INTERVAL + offsetY], heightMap[INTERPOLATION_INTERVAL + offsetX][offsetY], heightMap[INTERPOLATION_INTERVAL + offsetX][offsetY + INTERPOLATION_INTERVAL], offsetX, INTERPOLATION_INTERVAL + offsetX, offsetY, INTERPOLATION_INTERVAL + offsetY);
                }
            }
        }

        for (int x = 0; x < Configuration.CHUNK_DIMENSIONS.x; x++) {
            for (int z = 0; z < Configuration.CHUNK_DIMENSIONS.z; z++) {
                int height = (int) (128 * heightMap[x][z]);

                boolean first = true;
                for (int i = (int) Configuration.CHUNK_DIMENSIONS.y; i >= 0; i--) {

                    if (first && i == height) {
                        first = false;
                        // Generate grass on the top layer
                        c.setBlock(x, i, z, getBlockTailpiece(c, getBlockTypeForPosition(c, x, i, z, height), i));

                    } else if (i < height) {
                        c.setBlock(x, i, z, getBlockTypeForPosition(c, x, i, z, height));
                    }

                    /*
                     * Generate the "ocean".
                     */
                    if (i < 32 && i > 0) {
                        if (c.getBlock(x, i, z) == 0) {
                            c.setBlock(x, i, z, (byte) 0x4);
                        }
                    }

                    /*
                     * Generate hard stone layer.
                     */
                    if (i == 0) {
                        c.setBlock(x, i, z, (byte) 0x8);
                    }
                }
            }
        }
    }

    /**
     * 
     * @param c
     * @param type
     * @param y
     * @return
     */
    public byte getBlockTailpiece(Chunk c, byte type, int y) {
        // Sand
        if (type == 0x7) {
            return 0x7;
        } else if (type == 0x3) {
            return 0x3;
        }

        // No grass below the water surface
        if (y > 32) {
            return 0x1;
        } else {
            return 0x2;
        }
    }

    /**
     * 
     * @param c
     * @param x
     * @param y
     * @param z
     * @param height
     * @return
     */
    public byte getBlockTypeForPosition(Chunk c, int x, int y, int z, int height) {
        // Sand
        if (y >= 28 && y <= 32) {
            return (byte) 0x7;
        }

        if ((float) y / (float) height < 0.85) {
            return (byte) 0x3;
        }

        return 0x2;
    }

    /**
     * 
     * @param x
     * @param z
     * @return 
     */
    public float calcHeightMap(float x, float z) {
        float heightMap = (float) calcTerrainElevation(x, z) + (calcTerrainRoughness(x, z) * calcTerrainDetail(x, z)) + calcMountainDetail(x, z);
        return heightMap;
    }

    /**
     * Returns the base elevation for the terrain.
     * 
     * @param x
     * @param z
     * @return
     */
    protected float calcTerrainElevation(float x, float z) {
        float result = 0.0f;
        result += _pGen1.noise(0.0009f * x, 0.0009f, 0.0009f * z) * 0.95f;
        return result;
    }

    /**
     * Returns the roughness for the base terrain.
     * 
     * @param x
     * @param z
     * @return
     */
    protected float calcTerrainRoughness(float x, float z) {
        float result = 0.0f;
        result += _pGen2.noiseWithOctaves(0.009f * x, 0.009f, 0.009f * z, 16, 0.25f, 2f) * 0.1f;


        return result;
    }

    /**
     * Returns the detail level for the base terrain.
     * 
     * @param x
     * @param z
     * @return
     */
    protected float calcTerrainDetail(float x, float z) {
        float result = 0.0f;
        result += _pGen3.ridgedMultiFractalNoise(x * 0.008f, 0.008f, z * 0.008f, 8, 1.2f, 3f, 1f) * 0.6;
        return result;
    }

    /**
     * TODO
     * 
     * @param x
     * @param z
     * @return
     */
    protected float calcMountainDetail(float x, float z) {
        float result = 0.0f;
        result += _pGen1.noiseWithOctaves(x * 0.01f, 0.01f, z * 0.01f, 16, 1.2f, 2f);
        
        if (result < 0.5) {
            result = 0f;
        }
        return result / 2f;
    }
}
